#include "DocumentWindow.h"
#include "ShellMdiArea.h"

#include "simodo/lsp-client/LspEnums.h"

#include <QMainWindow>
#include <QtWidgets>

using namespace simodo;

DocumentWindow::DocumentWindow(MainWindow * main_window, QVector<shell::ViewAdaptor_interface *> views)
    : _main_window(main_window)
{
    Q_ASSERT(!views.empty());

    setContentsMargins(0,0,0,0);
    setFocusPolicy(Qt::NoFocus);

    for(int i=0; i < views.size(); ++i)
        _tab_params.append({views[i]});

    _tab_params[0].widget = _tab_params[0].view->document_widget();

    QVBoxLayout * main_layout = new QVBoxLayout(this);
    main_layout->setMargin(0);
    main_layout->setSpacing(0);
    main_layout->setContentsMargins(0,0,0,0);

    QWidget *     top_widget  = new QWidget;
    top_widget->setContentsMargins(0,0,0,0);

    _layout = new QVBoxLayout(top_widget);
    _layout->addWidget(_tab_params[0].widget);
    _layout->setMargin(0);
    _layout->setSpacing(0);
    _layout->setContentsMargins(0,0,0,0);

    if (views.size() > 1) {
        QTabBar * tab_bar = new QTabBar;
        tab_bar->setFocusPolicy(Qt::NoFocus);
        tab_bar->setShape(QTabBar::TriangularSouth);
        tab_bar->setDocumentMode(true);
        tab_bar->setDrawBase(false);
        tab_bar->setExpanding(false);
        for(int i=0; i < views.size(); ++i) {
            QString icon_theme = views[i]->plugin()->icon_theme();
            QIcon   icon       = QIcon::fromTheme(icon_theme, views[i]->plugin()->icon());
            tab_bar->addTab(icon, views[i]->plugin()->view_name());
        }
        tab_bar->setCurrentIndex(0);
        connect(tab_bar, &QTabBar::currentChanged, this, &DocumentWindow::onTabChanged);
        _layout->addWidget(tab_bar);
    }

    _main_splitter = new QSplitter;
    _main_splitter->setOrientation(Qt::Vertical);
    // splitter->setContentsMargins(0,0,0,0);
    // splitter->setFocusPolicy(Qt::NoFocus);
    _diagnostics = new QListWidget;
    _main_splitter->addWidget(top_widget);
    _main_splitter->addWidget(_diagnostics);
    _diagnostics->setVisible(false);
    _diagnostics->setFocusPolicy(Qt::NoFocus);
    _diagnostics->setWordWrap(true);

    main_layout->addWidget(_main_splitter);

    connect(_diagnostics, &QListWidget::itemActivated, this,
            [this](QListWidgetItem *item){
                QPoint pos = item->data(Qt::UserRole).toPoint();
                setLineCol({pos.x(), pos.y()});
            });

    // _tab_params[0].view->readyToWork(_tab_params[0].widget);
    _tab_params[0].widget->setFocus();
    setAttribute(Qt::WA_DeleteOnClose);
}

DocumentWindow::~DocumentWindow()
{
    _main_window->lsp().closed(_current_file);

    for(auto & p : _tab_params) {
        delete p.view;
        if (p.widget)
            delete p.widget;
    }
}

void DocumentWindow::regroupViews(unsigned orientation)
{
    if (_tab_params[_current_tab].orientation == orientation)
        return;

    QWidget * for_delete;

    if (orientation) {
        _layout->removeWidget(_tab_params[_current_tab].widget);
        for_delete = qobject_cast<QSplitter *>(_tab_params[_current_tab].widget);
        QSplitter * splitter = new QSplitter;
        splitter->setContentsMargins(0,0,0,0);
        splitter->setFocusPolicy(Qt::NoFocus);
        splitter->setOrientation(orientation == Qt::Vertical ? Qt::Vertical : Qt::Horizontal);
        splitter->addWidget(_tab_params[_current_tab].view->document_widget());
        QWidget * widget = _tab_params[_current_tab].view->copyWidget();
        splitter->addWidget(widget);
        _layout->insertWidget(0, splitter);
        _tab_params[_current_tab].widget = splitter;
        _tab_params[_current_tab].view->readyToWork(widget);
    }
    else {
        _layout->removeWidget(_tab_params[_current_tab].widget);
        for_delete = qobject_cast<QSplitter *>(_tab_params[_current_tab].widget);
        _tab_params[_current_tab].widget = _tab_params[_current_tab].view->document_widget();
        _layout->insertWidget(0, _tab_params[_current_tab].widget);
    }

    if (for_delete)
        delete for_delete;

    _tab_params[_current_tab].orientation = orientation;
    _tab_params[_current_tab].view->document_widget()->setFocus();
}

bool DocumentWindow::save()
{
    if (_is_untitled) {
        return saveAs();
    } else {
        return saveFile(_current_file);
    }
}

bool DocumentWindow::saveAs()
{
    QString lastName = _current_file;
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"), lastName);
    if (fileName.isEmpty())
        return false;

    bool ok = saveFile(fileName);
    if (ok) {
        /// @todo Нужно закрывать старый документ и открывать новый в LSP
        
        DocumentCollector &                               doc_collector    = _main_window->document_collector();
        std::shared_ptr<shell::DocumentAdaptor_interface> last_doc_adaptor = doc_collector.find(lastName);
        if (last_doc_adaptor)
            doc_collector.remove(lastName);
        doc_collector.add(fileName, _tab_params[0].view->document_adaptor());
    }
    return ok;
}

bool DocumentWindow::loadFile(const QString &fileName)
{
    QString error_string = _tab_params[0].view->loadFile(fileName);
    if (error_string.isEmpty()) {
        return true;
    }

    QString error_message = tr("Unable to load file '%1'. %2")
                    .arg(fileName)
                    .arg(error_string);
    QMessageBox::warning(this, "SIMODO", error_message);
    _main_window->sendGlobal(error_message, shell::MessageSeverity::Error);
    return false;
}

bool DocumentWindow::saveFile(const QString &fileName)
{
    QString error_string = _tab_params[0].view->document_adaptor()->saveFile(fileName);
    if (error_string.isEmpty()) {
        setCurrentFile(fileName);
        return true;
    }

    QString error_message = tr("Unable to save file '%1'. %2")
                    .arg(fileName)
                    .arg(error_string);
    QMessageBox::warning(this, "SIMODO", error_message);
    _main_window->sendGlobal(error_message, shell::MessageSeverity::Error);
    return false;
}

// bool DocumentWindow::openView(const std::vector<simodo::lsp::SimodoCommandReport> & reports)
// {
//     if (reports.empty())
//         return false;

//     QMessageBox::information(this, QString::fromStdU16String(reports[0].name), QString::fromStdU16String(reports[0].text));

//     return true;
// }

/// \todo Возможно, пригодится. Этот метод использовался, если создавался простой новый файл. 
/// Он был убран, т.к. хорошо работал для текстового редактора, а в оболочке могут быть любые документы.
/// Однако можно предусмотреть такой простой способ для текстового редактора "по умолчанию".

// void DocumentWindow::newFile(const QString & path)
// {
//     static int sequenceNumber = 1;

//     _is_untitled = true;
//     _current_file = tr("document%1.%2").arg(sequenceNumber++).arg(extention);
//     setWindowTitle(_current_file + "[*]");
// }

QString DocumentWindow::userFriendlyCurrentFile()
{
    return strippedName(_current_file);
}

void DocumentWindow::publishDiagnostics(int version, QVector<shell::Diagnostic>& diagnostics, int checksum)
{
    Q_UNUSED(version);
    Q_UNUSED(checksum);

    if (diagnostics.empty()) {
        _diagnostics->setVisible(false);
        return;
    }

    _diagnostics->setVisible(true);
    _diagnostics->clear();

    for(const shell::Diagnostic & d : diagnostics) {
        std::u16string    severity_str  = lsp::getDiagnosticSeverityString(d.severity);
        QString           severity_qstr = QString::fromStdU16String(severity_str);
        QStringList       message_list  = d.message.split("\n");
        QString           message       = message_list.front();
        QListWidgetItem * item = new QListWidgetItem(QIcon::fromTheme("dialog-"+severity_qstr, QIcon(":/images/dialog-"+severity_qstr)),
                                                     QString("%1: %2 [%3, %4]")
                                                        .arg(severity_qstr)
                                                        .arg(message)
                                                        .arg(d.range.start().line()+1)
                                                        .arg(d.range.start().character()+1)
                                                    );
        item->setData(Qt::UserRole, QPoint(d.range.start().line()+1,d.range.start().character()+1));

        QString tool_tip;
        for(int i=1; i < message_list.size(); ++i) {
            if (!tool_tip.isEmpty())
                tool_tip += "\n";
            tool_tip += message_list.at(i);
        }
        if (!tool_tip.isEmpty())
            item->setToolTip(tool_tip);

        _diagnostics->addItem(item);
    }

    if (!_diagnostics_size_trigger) {
        QList<int> sizes = _main_splitter->sizes();

        if (!sizes.empty()) 
            _main_splitter->setSizes({100*sizes.front()/10, 100*sizes.front()/90});

        _diagnostics_size_trigger = true;
    }
}

void DocumentWindow::closeEvent(QCloseEvent *event)
{
    if (maybeSave()) {
        _main_window->displayDocumentInfo("");
        _main_window->displayCursorPosition("");
        _main_window->displayStatistics("");

        for(TabParameters & tab : _tab_params)
            tab.view->nearToClose();

        event->accept();
    } else {
        event->ignore();
    }
}

void DocumentWindow::wheelEvent(QWheelEvent * event)
{
    if (event->modifiers() == Qt::ControlModifier) {
        if (event->angleDelta().y() > 0)
            zoomIn();
        else
            zoomOut();

        /// @todo Не работает. Продолжает прокручивать до упора в редакторах и в просмотре картинок
        event->ignore();
        return;
    }

    QWidget::wheelEvent(event);
}

bool DocumentWindow::maybeSave()
{
    if (!_tab_params[0].view->document_adaptor()->isModified())
        return true;
    const QMessageBox::StandardButton ret
            = QMessageBox::warning(this, "SIMODO",
                                   tr("'%1' has been modified.\n"
                                      "Do you want to save your changes?")
                                   .arg(userFriendlyCurrentFile()),
                                   QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
    switch (ret) {
    case QMessageBox::Save:
        return save();
    case QMessageBox::Discard:
        _main_window->document_collector().remove(_current_file);
        break;
    case QMessageBox::Cancel:
        return false;
    default:
        break;
    }
    return true;
}

void DocumentWindow::setCurrentFile(const QString &fileName)
{
    _current_file = QFileInfo(fileName).canonicalFilePath();
    _is_untitled = false;
    setWindowTitle(userFriendlyCurrentFile() + "[*]");
    setWindowModified(_tab_params[0].view->document_adaptor()->isModified());
}

QString DocumentWindow::strippedName(const QString &fullFileName)
{
    const ShellMdiArea & mdi_area = _main_window->mdi_area();
    const std::vector<QString> & history = mdi_area.history(); 

    for(size_t i=0; i < history.size(); ++i)
        if (fullFileName != history[i] && QFileInfo(fullFileName).fileName() == QFileInfo(history[i]).fileName()) 
            return QStringList {QFileInfo(fullFileName).dir().dirName(), QFileInfo(fullFileName).fileName()}.join("/");

    return QFileInfo(fullFileName).fileName();
}

void DocumentWindow::onTabChanged(int tab_no)
{
    if (_current_tab == tab_no)
        return;

    _layout->removeWidget(_tab_params[_current_tab].widget);
    _tab_params[_current_tab].widget->setVisible(false);
    if (_tab_params[tab_no].widget == nullptr) {
        _tab_params[tab_no].widget = _tab_params[tab_no].view->document_widget();
        _tab_params[tab_no].view->document_adaptor()->setText(
                                        _tab_params[_current_tab].view->document_adaptor()->text());
    }
    else if (_tab_params[_current_tab].view->document_adaptor()->isModified()) {
        _tab_params[tab_no].view->document_adaptor()->setText(
                                        _tab_params[_current_tab].view->document_adaptor()->text());
    }
    _tab_params[tab_no].view->document_adaptor()->setModified(
                                    _tab_params[_current_tab].view->document_adaptor()->isModified());
    _tab_params[tab_no].view->setLineCol(_tab_params[_current_tab].view->getLineCol());
    _layout->insertWidget(0, _tab_params[tab_no].widget);
    _tab_params[tab_no].widget->setVisible(true);
    _current_tab = tab_no;
    _tab_params[_current_tab].view->readyToWork(_tab_params[_current_tab].view->document_widget());
    _tab_params[_current_tab].view->document_widget()->setFocus();
}

